const net = require('net');
const setAttrType = require('./setAttrType');
const {
  Event
} = require('./EventAggregator');
const http = require('http');
const uuid = require('node-uuid');
const fs = require('fs');
const tls = require('tls');
const ws = require('ws');
const path = require('path');

class TcpServer {
  constructor(config, portConfig) {
    // 常量区域
    const __DEFAULTCONFIG__ = {
      ip: '',
      readycb: null,
      routingTable: null, //路由规则
      giveOut: true,
    };
    const __TIMEOUT__ = 100000;//网关时间过长断开链接的时间

    if (!config.ip) throw new Error('必须提供一个ip作为参数');

    config = Object.assign(__DEFAULTCONFIG__, config);
    const _this = this;
    //所拥有的事件
    const _nowEvents = [
      new Event('seviceReady'), //当服务准备好的事件
      new Event('routingTableChange'), //当路由表改变的事件
      new Event('newDeviceConnect'), //当新设备连接的事件
      new Event('newAppConnect'), //当新的APP设备连接的事件
      new Event('newGateWayConnect'), //当新的网关连接的事件
      new Event('websocketConnect'), //当i新的ws连接的时候
      new Event('newHTTPConnection'), //当新的http服务连接的时候，TCP 流被建立
      new Event('newHTTPconnect'), //当新的http服务连接的时候
      new Event('gateWayTimeout'), //当网关因为超时而失去连接的事件
      new Event('getDataGateWay'), //当收到来自网关的数据的时候，不包括之前的名字验证
      new Event('getDataFromApp'), //当收到来自APP的数据的时候，不包括之前的验证数据接受
      new Event('getDataFromhttp'), //当接受到来自http的数据的时候
      new Event('getDataFromWebSocket'), //当接受到来自于ws的数据的时候
      new Event('giveOutData'), //当分发数据的时候
      new Event('gateWayBreakLink'), //当网关设备断开链接
      new Event('appWayBreakLink'), //当APP设备断开链接
      new Event('websocketBreakLink'), //当websockt设备断开链接
      new Event('deviceBreakLink') //设备断开连接的时候
    ];
    //tsl配置项
    const tlsOptions = {
      key: fs.readFileSync(path.join(__dirname + '/server/server-key.pem')),
      cert: fs.readFileSync(path.join(__dirname + '/server/server-cert.pem')),
    };
    //路由规则表
    let _routingTable = Array.isArray(config.routingTable) ? config.routingTable : [];

    //端口设置相关
    portConfig = Object.assign({
        //默认各个服务对应的端口号
        gateway: [18231, 18232],
        app: [18331],
        http: [18082],
        ws: [18333],
      },
      portConfig
    );

    let _ConnectDevices = []; //已连接设备const,只包括app gateway

    let _services = []; //目前拥有的服务

    //属性设置，包括类型，权限设置
    setAttrType(this, {
      ip: {
        //服务器ip属性：只读
        get: function() {
          return config.ip;
        },
      },
      giveOut: {
        //服务器是否启动分发模式
        get: () => _routingTable,
        set: value => {
          _routingTable = value;
          getEventByName('routingTableChange').fire(_this);
        },
      },
      nowEvents: {
        //获得目前服务器所拥有的所有事件名称
        get: () => _nowEvents.map(event => event.getName()),
      },
    });

    //相关服务设置
    function setListenPort(ports, type) {
      //设置服务监听端口
      if (!Array.isArray(ports)) {
        ports = [ports];
      }
      //为每个端口设置对应的服务
      ports.forEach(function(port) {
        let server = null;
        switch (type) {
          case 'ws':
          case 'http':
            server = http.createServer();
            break;
          case 'gateway':
          case 'app':
            server = net.createServer();
            break;
        }
        server.listen(port, () => console.log(`${type}   服务已监听${port}端口`));
        let service = {
          type,
          server,
          users: [],
        };
        if (type === 'ws') {
          service.websocketServer = new ws.Server({
            server
          });
        }
        _services.push(service);
      });

      //当端口被占用切换备用端口的函数。监听端口
      // function setServerListen (index) {
      //   //设置对应的服务的端口。
      //   // let hasSet = false;
      //   // if (!hasSet) {
      //   //   if (index < ports.length) {
      //   //     server.listen (ports[index], function (err) {
      //   //       if (err) {
      //   //         console.error (
      //   //           `端口${ports[index]}已被占用，正在为${type}切换备用端口${ports[++index]}｝`
      //   //         );
      //   //         setServerListen (++index);
      //   //       } else {
      //   //         hasSet = true;
      //   //         console.log (`已成功为${type}监听端口：   ${ports[index]}`);
      //   //       }
      //   //     });
      //   //   } else {
      //   //     console.log (`${type}监听失败，所有备选端口都被占用`);
      //   //   }
      //   // }
      // }
      // setServerListen (0);
    }
    //根据事件名字获得对应的内部事件
    function getEventByName(name) {
      let event = null;
      let EventArr = _nowEvents.filter(event => event.getName() === name);
      if (!EventArr.length) {
        throw new Error('没有对应事件:' + name + '，请添加后再来');
      } else {
        event = EventArr[0];
      }
      return event;
    }
    //对每一个Service都做传入的函数操作，可以通过type操作范围作筛选默认是全部
    function travalServices(fn, type) {
      //遍历所有的服务，对每一个服务（server）执行对应的操作一遍对应的擦偶哦
      let travaledServices = diffService(type);
      travaledServices.forEach(service => {
        fn(service);
      });
    }
    //选出对应类型的Services
    function diffService(type) {
      return type ? _services.filter(service => service.type === type) : _services;
    }

    //分发具体操作
    function giveOut(data, client, service) {
      let nowRule = _routingTable.filter(
        routeRule => routeRule.client === client
      )[0];
      let sendClient = [];
      _routingTable.forEach(function(rule) {
        if (rule.deviceId === nowRule.deviceId && rule.client !== nowRule.client) {
          sendClient.push(rule.client);
        }
      });
      if (sendClient.length) {
        sendClient.forEach(function(client) {
          client.write(data);
        });
      }
    }
    //注册路由规则
    function registRoutingTable({
      deviceId,
      client,
      address,
      sessionIdContext
    }) {
      let newRoutingRule = {};
      newRoutingRule.id = uuid.v1();
      newRoutingRule.deviceId = deviceId;
      newRoutingRule.client = client;
      newRoutingRule.address = address;
      newRoutingRule.sessionIdContext = sessionIdContext;
      _routingTable.push(newRoutingRule);
    }
    //删除路由规则,也就是删除完所有路由中的设备id记录
    function destroyRoutingTable(deviceId) {
      let ruleIndex = routingTable.findIndex(
        routingRule => routingRule.deviceId === deviceId
      );
      if (ruleIndex !== -1) {
        routingTable.splice(ruleIndex, 1);
        destroyRoutingTable(deviceId);
      }
    }
    //检查当前客户端是否之前已经连接进过设备
    function CheckClientIsLink(address, port, sessionIdContext) {
      return _ConnectDevices.filter(
        device =>
        device.address === address &&
        device.port === port &&
        device.sessionIdContext === sessionIdContext
      ).length;
    }
    //分发
    function onGiveOut(data, client, service) {
      getEventByName('giveOutData').fire(data, client, service);
      giveOut(data, client, service);
    }
    //当tls链接的设备接入的时候
    function ontslConnect(clientInfo, data, client, service) {
      let data = data.toString();
      try {
        data = JSON.parse(data);
        let newConnectDevice = {
          address: clientInfo.address,
          port: clientInfo.port,
          client: client,
          deviceId: data.deviceId,
          type: service.type,
          sessionIdContext: service.server.sessionIdContext,
        };
        _ConnectDevices.push(newConnectDevice);
        registRoutingTable(newConnectDevice);
        let EventName = service.type === 'gateway' ? 'GateWay' : 'App';
        service.type === 'gateway' ?console.log('新的网关接入') : console.log('新的APP接入');
        service.users.push(newConnectDevice)
        getEventByName('new' + EventName + 'Connect').fire('_ConnectDevices');
        getEventByName('newDeviceConnect').fire(service);
      } catch (e) {
        if (e) throw e;
        if (e) client.write('请给我json格式的数据，其中包含deviceId');
        console.log('对方发来的数据有问题，没法确认设备ID已发文提示');
      }
    }
    //当新的网关接入的时候
    function onNewGateWayConnect() {
      function gateWayConnect(service) {
        let server = service.server;
        server.on('connection', client => {
          client.on('data', function(data) {
            let clientInfo = this.address();
            if (!CheckClientIsLink(
                clientInfo.address,
                clientInfo.port,
                server.sessionIdContext
              )) {
              ontslConnect(clientInfo, data, client, service);
            } else {
              onGetDataFromGateWay(data, client, service);
              if (config.giveOut) {
                onGiveOut(data, client, service);
              }
            }
          });
        });
      }
      travalServices(gateWayConnect, 'gateway');
    }
    //当新的APP接入的时候
    function onNewAPPConnect() {
      function appConnect(service) {
        let server = service.server;
        server.on('connection', client => {
          client.on('data', function(data) {
            let clientInfo = this.address();
            if (!CheckClientIsLink(
                clientInfo.address,
                clientInfo.port,
                server.sessionIdContext
              )) {
              ontslConnect(clientInfo, data, client, service);
            } else {
              onGetDataFromApp(data, client, service);
              if (config.giveOut) {
                onGiveOut(data, client, service);
              }
            }
          });
        });
      }
      travalServices(appConnect, 'app');
    }
    //当新的Http接入的时候
    function onNewHTTPConnection() {
      travalServices(function(service) {
        service.server.on('connection', function(client) {
          console.log('新的Http接入');
          getEventByName('newHTTPConnection').fire(client);
          client.on('data', function(data) {
            onGetDataFromhttp(data, client, service);
          });
        });
      }, 'http');
    }

    function onNewHTTPconnect(req, res, head) {
      travalServices(function(service) {
        service.server.on('connect', function(req, res, head) {
          getEventByName('newHTTPConnect').fire(req, res, head);
        });
      });
    }
    //当新的websocket链接的时候
    function onWebsocketConnect() {
      travalServices(function(service) {
        service.websocketServer.on('connection', function(ws) {
          getEventByName('websocketConnect').fire(ws);
          let newConnectDevice = {
            address: clientInfo.address,
            port: clientInfo.port,
            client: ws,
            type: service.type,
          };
          _ConnectDevices.push(newConnectDevice);
          ws.on('message', function(message) {
            onGetDataFromWebSocket(message, ws);
          });
        });
      }, 'ws');
    }
    //当新设备接入的时候
    function onNewDeviceConnect() {
      //gateway连入监听
      onNewGateWayConnect();
      //app连入监听
      onNewAPPConnect();
      //新的http接入
      onNewHTTPConnection();
      onNewHTTPconnect();
      //新的ws接入
      onWebsocketConnect();
    }
    //当tsl设备断开链接
    function onTSldeviceBreakLink(service) {
      service.server.on('connection', function(client) {
        let _this = this;
        client.on('close', function() {
          let sessionIdContext = _this.sessionIdContext;
          let indexDevice = _ConnectDevices.findIndex(function(device) {
            return device.sessionIdContext === sessionIdContext;
          })
          let indexRule = _routingTable.findIndex(function(rule) {
            return rule.sessionIdContext === sessionIdContext;
          })
          let deviceInfo = {
            address: _ConnectDevices[indexDevice].address,
            port: _ConnectDevices[indexDevice].port,
            deviceId: _ConnectDevices[indexDevice].deviceId
          }
          console.log(`A ${service.type} breaklink,ip is ${deviceInfo.address}:${deviceInfo.port},diviceId is ${deviceInfo.deviceId}`)
          _ConnectDevices.splice(indexDevice, 1);
          _routingTable.splice(indexRule, 1);
          service.type === 'gateway' ? getEventByName('gateWayBreakLink').fire(deviceInfo) : getEventByName('appWayBreakLink').fire(deviceInfo);
        })
      })
    }
    //当网关设备断开链接
    function onGateWayBreakLink() {
      travalServices(onTSldeviceBreakLink, 'gateway');
    }
    //当APP设备断开链接
    function onAppBreakLink() {
      travalServices(onTSldeviceBreakLink, 'app');
    }
    //当websockt设备断开链接
    function onWebsocketBreakLink() {
      travalServices(function(service) {
        service.websocketServer.on('connection', function(ws) {
          ws.on('close', function(close) {
            getEventByName('websocketBreakLink').fire(close);
            let deviceIndex = _ConnectDevices.findIndex(
              device => device.client === ws
            );
            _ConnectDevices.splice(deviceIndex, 1);
          });
        });
      }, 'ws');
    }
    //当设备断开的时候
    function onDeviceBreakLink() {
      onGateWayBreakLink();
      onAppBreakLink();
      onWebsocketBreakLink();
    }
    //当网关超时未相应的时候
    function onGateWayTimeout() {
      travalServices(function(service) {
        //设置网关过时时间
        service.server.on('connection', function(client) {
          client.setTimeout(__TIMEOUT__);
          client.on('timeout', function() {
            client.end(function() {
              console.log('byebye');
            });
            getEventByName('gateWayTimeout').fire(service);
          });
          client.on('data', function(data) {
            console.log(data.toString());
          });
        });
      }, 'gateway');
    }
    //当接受到来自于网关的数据的时候
    function onGetDataFromGateWay(data, client, service) {
      getEventByName('getDataGateWay').fire(data, client, service);
    }
    //当接受到来自于APP的数据的时候
    function onGetDataFromApp(data, client, service) {
      getEventByName('getDataFromApp').fire(data, client, service);
    }
    //当接受到来自于http的数据的时候
    function onGetDataFromhttp(data, client, service) {
      getEventByName('getDataFromhttp').fire(data, client, service);
    }
    //当接受到来自于websockt的数据的时候
    function onGetDataFromWebSocket(data, ws) {
      getEventByName('getDataFromWebSocket').fire(data, ws);
    }
    //添加监听器
    this.on = (eventName, fn) => {
      getEventByName(eventName).addHandler(fn);
      return this;
    };
    //移除监听器
    this.un = (eventName, handlerName) => {
      getEventByName(eventName).removeHander(handlerName);
      return this;
    };

    //获取端口设置项，没有则返回全数组,返回的都是一个数组
    this.getPortConfig = DeviceName =>
      DeviceName ? portConfig[DeviceName] : [portConfig];
    //设置接口项，传入一个对象进行设置覆盖    这个要继续做就狠吗了。。实用性不大，动了要加太多东西。。心累
    // this.setPortConfig = ({newPortConfig}) =>
    //   (portConfig = Object.assign (portConfig, newPortConfig));

    //获取目前连接的设备，可通过设备类型进行筛选
    this.getConnectDevices = type =>
      type ?
      _ConnectDevices :
      _ConnectDevices.filter(device => type === device.name);

    this.destroyRoutingTable = destroyRoutingTable;
    //初始化
    function init() {
      for (let type in portConfig) {
        //根据设置参数开出相应服务监听对应端口。
        setListenPort(portConfig[type], type);
        Object.defineProperty(_this, type + 'Service', {
          //为TCP服务器绑定对应的只能读取的各项服务，通过服务名可以在对象上拿到
          get: function() {
            return services.filter(service => service.type == type);
          },
        });
      }
      let seviceReadyEvent = getEventByName('seviceReady');
      if (config.readycb) {
        seviceReadyEvent.addHandler(config.readycb);
      }
      setTimeout(function() {
        seviceReadyEvent.fire(_services);
        onNewDeviceConnect();
        onGateWayTimeout();
        onDeviceBreakLink();
      }, 0);
    }
    init();
  }
}
module.exports = TcpServer;